﻿/*----------------------------------------------------------------
//  <copyright file="FunctionAuthCacheBase.cs" company="MicroCloud@151504200868">
//      Copyright © 2020-2024 MicroCloud Corporation, All rights reserved.
//  </copyright>
//  <site>https://gitee.com/chenmm123/microclouds</site>
//  <last-editor>cmm</last-editor>
//  <last-date>2023-12-06 09:17</last-date>
//----------------------------------------------------------------*/

namespace MicroCloud.Authorization
{
    #region "功能权限配置缓存基类"
    /// <summary>
    /// 功能权限配置缓存基类
    /// </summary>
    /// <typeparam name="TModuleFunction">模块功能类型</typeparam>
    /// <typeparam name="TModuleRole">模块角色类型</typeparam>
    /// <typeparam name="TModuleUser">模块用户类型</typeparam>
    /// <typeparam name="TFunction">功能类型</typeparam>
    /// <typeparam name="TModule">模块类型</typeparam>
    /// <typeparam name="TModuleKey">模块编号类型</typeparam>
    /// <typeparam name="TRole">角色类型</typeparam>
    /// <typeparam name="TRoleKey">角色编号类型</typeparam>
    /// <typeparam name="TUser">用户类型</typeparam>
    /// <typeparam name="TUserKey">用户编号类型</typeparam>
    public abstract class FunctionAuthCacheBase<TModuleFunction, TModuleRole, TModuleUser, TFunction, TModule, TModuleKey, TRole, TRoleKey, TUser, TUserKey> : IFunctionAuthCache
        where TFunction : class, IFunction
        where TModule : ModuleBase<TModuleKey>
        where TModuleFunction : ModuleFunctionBase<TModuleKey>
        where TModuleKey : struct, IEquatable<TModuleKey>
        where TModuleRole : ModuleRoleBase<TModuleKey, TRoleKey>
        where TModuleUser : ModuleUserBase<TModuleKey, TUserKey>
        where TRole : RoleBase<TRoleKey, TUserKey>
        where TRoleKey : struct, IEquatable<TRoleKey>
        where TUser : UserBase<TUserKey>
        where TUserKey : struct, IEquatable<TUserKey>
    {
        //字段
        private readonly Random _random = new();
        private readonly IServiceProvider _serviceProvider;
        private readonly IDistributedCache _cache;
        private readonly ILogger _logger;

        #region "构造函数"
        #region "初始化一个功能权限配置缓存基类的新实例"
        /// <summary>
        /// 初始化一个功能权限配置缓存基类 <see cref="FunctionAuthCacheBase{TModuleFunction, TModuleRole, TModuleUser, TFunction, TModule, TModuleKey,TRole, TRoleKey, TUser, TUserKey}"/> 的新实例
        /// </summary>
        /// <param name="serviceProvider"></param>
        protected FunctionAuthCacheBase(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
            _cache = serviceProvider.GetRequiredService<IDistributedCache>();
            _logger = serviceProvider.GetLogger(GetType());
        }
        #endregion

        #endregion

        #region "方法"
        #region "创建功能权限缓存，只创建 功能-角色集合 的映射，用户-功能 的映射，遇到才即时创建并缓存"
        /// <summary>
        /// 创建功能权限缓存，只创建 功能-角色集合 的映射，用户-功能 的映射，遇到才即时创建并缓存
        /// </summary>
        public virtual void BuildRoleCaches()
        {
            //只创建 功能-角色集合 的映射，用户-功能 的映射，遇到才即时创建并缓存
            _serviceProvider.ExecuteScopedWork(provider =>
            {
                var functionRepository = provider.GetRequiredService<IRepository<TFunction, long>>();
                var functionIds = functionRepository.QueryAsNoTracking(null, false).Select(m => m.Id).ToArray();
                foreach (var functionId in functionIds)
                {
                    GetFunctionRoles(functionId, provider, true);
                }
                _logger.LogInformation("构建 {length} 个功能权限的 Function-Roles[] 缓存", functionIds.Length);
            });
        }
        #endregion
        #region "移除指定功能的缓存"
        /// <summary>
        /// 移除指定功能的缓存
        /// </summary>
        /// <param name="functionIds">功能编号集合</param>
        public virtual void RemoveFunctionCaches(params long[] functionIds)
        {
            foreach (var functionId in functionIds)
            {
                string key = GetFunctionRolesKey(functionId);
                _cache.Remove(key);
                _logger.LogDebug("移除功能 {functionId} 的 Function-Roles[] 缓存", functionId);
            }
            _logger.LogInformation("移除 {length} 个功能权限的 Function-Roles[] 缓存", functionIds.Length);
        }
        #endregion
        #region "移除指定用户的缓存"
        /// <summary>
        /// 移除指定用户的缓存
        /// </summary>
        /// <param name="userIds">用户编号集合</param>
        public virtual void RemoveUserCaches(params string[] userIds)
        {
            foreach (string userId in userIds)
            {
                string key = GetUserFunctionsKey(userId);
                _cache.Remove(key);
            }
        }
        #endregion
        #region "获取能执行指定功能的所有角色"
        /// <summary>
        /// 获取能执行指定功能的所有角色
        /// </summary>
        /// <param name="functionId">功能编号</param>
        /// <param name="scopeProvider">局部服务提供者</param>
        /// <param name="forceRefresh">是否强制刷新</param>
        /// <returns>能执行功能的角色编号集合</returns>
        public string[] GetFunctionRoles(long functionId, IServiceProvider scopeProvider = null, bool forceRefresh = false)
        {
            string key = GetFunctionRolesKey(functionId);
            string[] roleIds;
            if (!forceRefresh)
            {
                roleIds = _cache.Get<string[]>(key);
                if (roleIds != null)
                {
                    _logger.LogDebug("从缓存中获取到功能 {functionId} 的 Function-Roles[] 缓存，角色数 {length}。", functionId, roleIds.Length);
                    return roleIds;
                }
            }

            IServiceProvider provider = scopeProvider;
            IServiceScope serviceScope = null;
            if (provider == null)
            {
                serviceScope = _serviceProvider.CreateScope();
                provider = serviceScope.ServiceProvider;
            }

            var moduleFunctionRepository = provider.GetRequiredService<IRepository<TModuleFunction, long>>();
            TModuleKey[] moduleIds = moduleFunctionRepository.QueryAsNoTracking(m => m.FunctionId.Equals(functionId)).Select(m => m.ModuleId).Distinct().ToArray();
            if (moduleIds.Length == 0)
            {
                serviceScope?.Dispose();
                serviceScope = null;
                return Array.Empty<string>();
            }

            roleIds = Array.Empty<string>();
            var moduleRoleRepository = provider.GetRequiredService<IRepository<TModuleRole, long>>();

            //TRoleKey[] roleIds = moduleRoleRepository.QueryAsNoTracking(m => moduleIds.Contains(m.ModuleId)).Select(m => m.RoleId).Distinct().ToArray();
            //if (roleIds.Length > 0)
            //{
            //    IRepository<TRole, TRoleKey> roleRepository = provider.GetRequiredService<IRepository<TRole, TRoleKey>>();
            //    roleNames = roleRepository.QueryAsNoTracking(m => roleIds.Contains(m.Id)).Select(m => m.Name).Distinct().ToArray();
            //}
            roleIds = moduleRoleRepository.QueryAsNoTracking(m => moduleIds.Contains(m.ModuleId)).Select(m => m.RoleId.ToString()).Distinct().ToArray();

            // 有效期为 7 ± 1 天
            int seconds = 7 * 24 * 3600 + _random.Next(-24 * 3600, 24 * 3600);
            _cache.Set(key, roleIds, seconds);
            _logger.LogDebug("添加功能 {functionId} 的 Function-Roles[] 缓存，角色数 {length}。", functionId, roleIds.Length);

            serviceScope?.Dispose();
            serviceScope = null;
            return roleIds;
        }
        #endregion
        #region "获取指定用户的所有特权功能"
        /// <summary>
        /// 获取指定用户的所有特权功能
        /// </summary>
        /// <param name="userId">用户编号</param>
        /// <returns>用户的所有特权功能编号集合</returns>
        public virtual long[] GetUserFunctions(string userId)
        {
            string key = GetUserFunctionsKey(userId);
            long[] functionIds = _cache.Get<long[]>(key);
            if (functionIds != null)
            {
                _logger.LogDebug("从缓存中获取到用户 {userId} 的 User-Function[] 缓存", userId);
                return functionIds;
            }
            functionIds = _serviceProvider.ExecuteScopedWork(provider =>
            {
                var userRepository = provider.GetRequiredService<IRepository<TUser, TUserKey>>();
                var userKey = userRepository.QueryAsNoTracking(m => m.Id.Equals(userId.CastTo<TUserKey>())).Select(m => m.Id).FirstOrDefault();
                if (Equals(userKey, default(TUserKey)))
                {
                    return Array.Empty<long>();
                }
                var moduleUserRepository = provider.GetRequiredService<IRepository<TModuleUser, long>>();
                var moduleIds = moduleUserRepository.QueryAsNoTracking(m => m.UserId.Equals(userKey)).Select(m => m.ModuleId).Distinct().ToArray();
                var moduleRepository = provider.GetRequiredService<IRepository<TModule, TModuleKey>>();
                moduleIds = moduleIds.Select(m => moduleRepository.QueryAsNoTracking(n => n.TreePathString.Contains("$" + m + "$"))
                    .Select(n => n.Id)).SelectMany(m => m).Distinct().ToArray();
                var moduleFunctionRepository = provider.GetRequiredService<IRepository<TModuleFunction, long>>();
                return moduleFunctionRepository.QueryAsNoTracking(m => moduleIds.Contains(m.ModuleId)).Select(m => m.FunctionId).Distinct().ToArray();
            });

            // 有效期为 7 ± 1 天
            int seconds = 7 * 24 * 3600 + _random.Next(-24 * 3600, 24 * 3600);
            _cache.Set(key, functionIds, seconds);
            _logger.LogDebug("创建用户 {userId} 的 User-Function[] 缓存", userId);

            return functionIds;
        }
        #endregion

        #endregion

        #region "私有静态方法"
        #region "获取功能角色缓存键"
        /// <summary>
        /// 获取功能角色缓存键
        /// </summary>
        /// <param name="functionId">功能编号</param>
        /// <returns>功能角色缓存键</returns>
        private static string GetFunctionRolesKey(long functionId)
        {
            return $"Auth:Function:FunctionRoles:{functionId}";
        }
        #endregion
        #region "获取用户功能缓存键"
        /// <summary>
        /// 获取用户功能缓存键
        /// </summary>
        /// <param name="userId">用户编号</param>
        /// <returns>用户功能缓存键</returns>
        private static string GetUserFunctionsKey(string userId)
        {
            return $"Auth:Function:UserFunctions:{userId}";
        }
        #endregion

        #endregion

    }
    #endregion

}
